package com.hfh.server.netty_server;

import com.hfh.server.common.Constants;
import com.hfh.server.netty_server.service.NettyServerMessageService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author liuxinghong
 * @Description: 服务器端业务处理器
 * @date 2019/9/2 000214:25
 */
@Slf4j
public  class ServerHandler  extends SimpleChannelInboundHandler<Object> {

    @Autowired
    private NettyServerMessageService nettyServerMessageService;

    /** 心跳次数未接收到的初始值 */

    public static int idleCount=1 ;


    /**
     * channel的id 与 客户端的key 对应关系Ｏ
     */
    public static ConcurrentHashMap<String,Channel>  nettyChannelKey= new ConcurrentHashMap<>();//存放客户端key和channel
    public static ConcurrentHashMap<String,Integer>  clientHeatBeatCountMap= new ConcurrentHashMap<>();//通道id,客户端当前进行的心跳检测次数

    /**
     * 处理接收到的消息
     * @param channelHandlerContext 上下文
     * @param msg 消息体
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {

        MessageDTO message = (MessageDTO)msg;
        if (null != message && NettyConfig.NETTY_FIRST_CONNECT_TYPE.equals(message.getType()) &&!nettyChannelKey.containsKey(message.getSenderId())){
            //首次连接建立客户端和服务器端识别映射
            nettyChannelKey.put(message.getSenderId(),channelHandlerContext.channel());
            log.info("首次连接服务端身份识别映射map数据:\n key: "+message.getSenderId()+" value: "+nettyChannelKey.get(message.getSenderId()));
        }else if (null != message && NettyConfig.NETTY_HEARTBEAT_TYPE.equals(message.getType())){
            //心跳消息
           log.info("收到客户端心跳消息>>>"+"客户端识别key："+message.getSenderId()+"客户端ip: "+channelHandlerContext.channel().remoteAddress().toString().substring(1)+"消息内容："+message.getMessageBody());
        }else {
            if(1!=clientHeatBeatCountMap.get(channelHandlerContext.channel().id().asShortText())){
                clientHeatBeatCountMap.replace(channelHandlerContext.channel().id().asShortText(),1);//当收到客户端发来的任何消息时，设置服务端检测此客户端心跳检测次数为初始值
            }
            log.info("服务端收到客户端发来的业务消息：\n"+message.toString());
            //接入业务逻辑方法调用
            // todo：
            nettyServerMessageService.readMessage(message);
        }
    }

    /**
     * 消息处理完成 回复结果事件
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

//        MessageDTO messageDTO = NettyServerMessageAPI.readMessageReply();
//        log.info("消息回复客户端的数据 客户端ip:"+ctx.channel().remoteAddress());
//        if (null !=messageDTO){
//            ctx.channel().writeAndFlush(messageDTO);
//        }else {
//            log.info("回复客户端的数据为空，将不发送 客户端ip:"+ctx.channel().remoteAddress());
//        }
        ctx.fireChannelReadComplete();
        }

    /**
     * 出现异常后处理事件
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("netty服务端出现异常,请及时查看",cause.getMessage());
        cause.printStackTrace();

    }

    /**
     * 超时处理
     * 如果5秒没有接受客户端的心跳，就触发;
     * 如果超过两次，则直接关闭;
     */

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
        if (obj instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) obj;
            if (IdleState.ALL_IDLE.equals(event.state())) {  //如果读写所有通道处于空闲状态，说明没有接收到心跳命令
                String shortId = ctx.channel().id().asShortText();
                ;
                log.info("已经第"+clientHeatBeatCountMap.get(shortId)+"次没有接收到客户端: "+ctx.channel().remoteAddress().toString().substring(1) +" 的信息了"+" 当前ip:"+ctx.channel().localAddress()+" 当前时间:"+DateTime.now().toString(Constants.YYYY_MM_DD_HH_MM_SS));
                if (clientHeatBeatCountMap.get(shortId) >= NettyConfig.NETTY_SERVER_FCOUNT_TIMES) {
                    log.info("关闭一个不活跃的 客户端ip:"+ctx.channel().remoteAddress().toString().substring(1)+"\n 当前通道数据:"+ctx.channel().toString());
                    clientHeatBeatCountMap.remove(shortId); //移除集合里的通道
                    removeChannel(ctx); //移除映射关系里的数据
                    ctx.channel().close();
                }else {
                    clientHeatBeatCountMap.replace(ctx.channel().id().asShortText(),clientHeatBeatCountMap.get(shortId)+1);
                }
            }else if (IdleState.WRITER_IDLE.equals(event.state())){//如果写的通道处于空闲状态,就发送心跳命令

        } else if (IdleState.READER_IDLE.equals(event.state())){//如果du的通道处于空闲状态,就发送心跳命令

        }
    } else {
        super.userEventTriggered(ctx, obj);
    }
    }

    /**
     * 当channel被激活时，触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
       log.info(ctx.channel().remoteAddress()+" :连接到服务端 时间：" +DateTime.now().toString("YYYY-MM-dd HH:mm:ss"));
        clientHeatBeatCountMap.put(ctx.channel().id().asShortText(),1); //将客户端通道数据加入到集合中
        ctx.fireChannelActive();
    }

    /**
     * 当通道被销毁时  触发时间
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info(ctx.channel().remoteAddress()+" :从服务端离线 时间：" +DateTime.now().toString("YYYY-MM-dd HH:mm:ss"));
        clientHeatBeatCountMap.remove(ctx.channel().id().asShortText());//关闭通道
        //移除客户端与channel映射map的数据
        removeChannel(ctx);
        ctx.fireChannelInactive();
    }

    private void removeChannel(ChannelHandlerContext ctx) {
        if (!nettyChannelKey.isEmpty()) {
            HashSet<String> keySet = new HashSet<String>();
            for (Map.Entry<String, Channel> entity : nettyChannelKey.entrySet()) {
                if (entity.getValue() == ctx.channel()) {
                    keySet.add(entity.getKey());
                }
            }
            keySet.stream().forEach(item -> {
                nettyChannelKey.remove(item);
            });
        }
    }

}
